/**
 * Simulation of a single Web Server using queue model concepts and
 * the Simpack library. This model has just one facility with one
 * service center, that represents the Web Server, and just one queue.
 *
 * @author Lucas dos Santos Casagrande
 * @version 1.0 Created on 9 de Agosto de 2006, 15:30
 */

package queue_model;

import java.io.BufferedWriter;
import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import java.io.PrintWriter;
import java.math.BigDecimal;
import java.util.Random;

import simpackNew.Const;
import simpackNew.Sim;
import simpackNew.SimEvent;
import simpackNew.User;

public class QueueModel {

    SimEvent event; // Event Descriptor
    int tokenCount; // Used to Identify the requests and control the number of arrivals
    int server; // Server descriptor
    User[] users; // System's users

    int politics; // Type of schedule politics
    int preemptiveRequest; // Do or don't  preempt
    int simulNumber; // Simulation number
    
    /* Modified by Priscila - inicio */
    int users_number;
    int contracts_deviation;
    /* Modified by Priscila - fim */
    
    // -> int contractVariation;
    double[] contracts; // Contracted means per class, refering first to class A, then B, ...
    // final int percentageClassA; // Percentage of Class A requests
    Random rn;

    double fulfilledContracts[];	// Percentage of fulfilled contract per user
    double latenciesBelowAverage[];	// Percentage of latencies of a user below the contracted average

    File outputDirectory;

    FileWriter writer[];
    PrintWriter output[];
    
    /**
     * @param politics - Type of schedule politics
     * @param sN - Simulation Number
     * // @param percentageClassA - Percentage of Class A requests
     * @param contractVariation - Variation of the contract
     * @param contracts - Contracted means per class, refering first to class A, then B, ...
     * 
     * Obs.: The number of classes and contracts must be equal.
     * */
    public QueueModel(int politics, int preemptiveRequest, int sN, int contracts_deviation, int users_number, double... contracts) {
    // public QueueModel(int politics, int preemptiveRequest, int sN, int percentageClassA, int contractVariation, double... contracts) {
    	this.politics = politics;
    	this.preemptiveRequest = preemptiveRequest;
    	this.simulNumber = sN;
    	
    	/* Modified by Priscila - inicio */
    	this.contracts_deviation = contracts_deviation;
    	this.users_number = users_number;
    	// this.percentageClassA = percentageClassA;
    	// this.contractVariation = contractVariation;
    	this.fulfilledContracts = new double[users_number];
    	this.latenciesBelowAverage = new double[users_number];
    	// this.fulfilledContracts = new double[Const.N_USERS_A + Const.N_USERS_B];
    	// this.latenciesBelowAverage = new double[Const.N_USERS_A + Const.N_USERS_B];
    	/* Modified by Priscila - fim */
    	
    	this.contracts = new double[contracts.length];
    	System.arraycopy(contracts, 0, this.contracts, 0, contracts.length);
    	rn = new Random();
    	rn.setSeed((long) sN);
    	setupEnviroment();
    }

    /**
     * Modified by Priscila - inicio
     * Initializes the simulation environment, creates the
     * service center, event descriptor, users,
     * and tries to open I/O streams.
     */
    void setupEnviroment() {

    	Sim.init(0.0, Const.LINKED);
    	server = Sim.create_facility("Server", Const.N_SERVERS);
    	event = new SimEvent();
    	createUsers();
    	tokenCount = 0;

    	openStreams();

    	System.out.println("\nEnvironment configurated...");
    }

    
    void createUsers() {
    	int index, total;
    	if (contracts.length != users_number) {
    	// if (contracts.length != Const.N_CLASSES) {
    		System.err.println("Amount of contracts is incompatible with the amount of classes.");
    		System.exit(Const.CONTRACT_ERROR);
    	}

    	// total = Const.N_USERS;    	
    	// total = Const.N_USERS_A + Const.N_USERS_B;
    	users = new User[users_number];
    	// -> users = new User[total];
    	
    	/* Modified by Priscila - inicio */
    	for (index = 0; index < users_number; index++){    		
    	 	users[index] = new User(index, contracts[index]);    		
    	 }
    	
    	//for (index = 0; index < Const.N_USERS_A; index++)
    	// 	users[index] = new User(Const.CLASSE_A , index, contracts[Const.CLASSE_A]);
    	// for (; index < total; index++)
    	// 	users[index] = new User(Const.CLASSE_B, index, contracts[Const.CLASSE_B]);
    	// /* Modified by Priscila - fim */
    }

    
    /**
     * Opens I/O streams.
     * */
    void openStreams() {
    	int index;
    	int total = users_number;
    	String fileName[] = new String[total];

    	writer = new FileWriter[total];
    	output = new PrintWriter[total];

    	try {
    		File dataDIR = new File(Const.DATA_DIR);
		    File simulationDIR = new File(dataDIR, Const.SIMULATION_DIR + simulNumber);
		    File arrival_MeanDIR = new File(simulationDIR, Const.ARRIVAL_MEAN_DIR + Const.ARRIVAL_TIME);
		    
		    /* Modified by Priscila - inicio */
		    File userDIR = new File(arrival_MeanDIR, Const.USERS_NUMBER_DIR + users_number);		    
		    // File percentageDIR = new File(arrival_MeanDIR, Const.PERCENTAGE_DIR + percentageClassA);
		    File contracts_DeviationDIR = new File(userDIR, Const.CONTRACTS_DEVIATION_DIR + contracts_deviation);
		    /* Modified by Priscila  fim */
		    
		    switch (politics) {
		    	
		    	case Const.FIFO:
		    	
				for (index = 0; index < users_number; index++) {
				    fileName[index] = "FIFO_Mean_" + Const.ARRIVAL_TIME + "_user" + index + ".txt";
				    // fileName[index] = "FIFO_Mean_" + Const.ARRIVAL_TIME + "_perc" + percentageClassA + "_user" + index + ".txt";
				}
				
				outputDirectory = new File(contracts_DeviationDIR, "FIFO");
				break;
				/*	
				for (index = 0; index < Const.N_USERS_A; index++) {
				    fileName[index] = "FIFO_ClassA_Mean_" + Const.ARRIVAL_TIME
					    + "_perc" + percentageClassA + "_user" + index + ".txt";
				}
				for (; index < total; index++) {
				    fileName[index] = "FIFO_ClassB_Mean_" + Const.ARRIVAL_TIME
					    + "_perc" + percentageClassA + "_user" + index + ".txt";
				}
				outputDirectory = new File(percentageDIR, "FIFO");
				break;
				*/
			    case Const.EBS:
				
			    for (index = 0; index < users_number; index++) {
				    fileName[index] = "EBS_Mean_" + Const.ARRIVAL_TIME + "_user" + index + ".txt";
				    // fileName[index] = "EBS_Mean_" + Const.ARRIVAL_TIME + "_perc" + percentageClassA + "_user" + index + ".txt";
				}
							    
				/* for (index = 0; index < Const.N_USERS_A; index++) {
				    fileName[index] = "EBS_ClassA_Mean_" + Const.ARRIVAL_TIME
					    + "_perc" + percentageClassA + "_contr" + contractVariation + "_user" + index + ".txt";
				}
				for (; index < total; index++) {
				    fileName[index] = "EBs_ClassB_Mean_" + Const.ARRIVAL_TIME
					    + "_perc" + percentageClassA + "_contr"
					    + contractVariation + "_user" + index + ".txt";
				} */
				
				outputDirectory = new File(contracts_DeviationDIR, "EBS");
				break;
				
			    case Const.SJF:
				
			    for (index = 0; index < users_number; index++) {
				    fileName[index] = "SJF_Mean_" + Const.ARRIVAL_TIME + "_user" + index + ".txt";
				    // fileName[index] = "SJF_Mean_" + Const.ARRIVAL_TIME + "_perc" + percentageClassA + "_user" + index + ".txt";
				}
				
			    /* for (index = 0; index < Const.N_USERS_A; index++) {
				    fileName[index] = "SJF_ClassA_Mean_" + Const.ARRIVAL_TIME
					    + "_perc" + percentageClassA + "_user" + index + ".txt";
				}
				
			    for (; index < total; index++) {
				    fileName[index] = "SJF_ClassB_Mean_" + Const.ARRIVAL_TIME
					    + "_perc" + percentageClassA + "_user"
					    + index + ".txt";
				} */
			    
				outputDirectory = new File(contracts_DeviationDIR, "SJF");
				break;
			    
			    default:
			    	
			    System.err.format("%nBad type of schedule politics.%n");
				System.exit(Const.BAD_POLITCS_ERROR);
		    }

	    if (!outputDirectory.exists())
		outputDirectory.mkdirs();

	    // Output log files
	    for (index = 0; index < total; index++) {
		writer[index] = new FileWriter(new File(outputDirectory, fileName[index]));
		output[index] = new PrintWriter(new BufferedWriter(writer[index]));
	    }

		} catch (IOException e) {
		    System.err.println("Openning streams:" + e);
		    System.exit(Const.OPENNING_STREAMS_ERROR);
		}
    }

    
    /**
     * Closes I/O streams.
     * */
    void closeStreams() {
		try {
		    for (int index = 0; index < (users_number); index++) {
		    // for (int index = 0; index < (Const.N_USERS_A + Const.N_USERS_B); index++) {
		    	output[index].close();
		    	writer[index].close();
		    }
		} catch (IOException e) {
		    System.err.println("Closing files: " + e);
		    System.exit(Const.CLOSING_STREAMS_ERROR);
		}
    }

    
    /**
     * Configures and schedule the arrival of a new request.
     * */
    void newArrival(double time) {
		// Configures the request
		event.token.timeStamp = Sim.time() + time;
		event.token.load = Sim.expntl(Const.SERVICE_TIME, rn);
		event.token.owner = setUser();
		event.token.preemptStartTime = 0.0;
		event.token.attr[0] = ++tokenCount; // The token ID (attr[0]) MUST be different of zero
	
		// Configure the arrival event
		event.id = Const.ARRIVAL;
		Sim.schedule(event, time);
    }
    
    
    /**
     * @return Returns some type of class based on the CLASS_PERCENTAGE.
     * */
    
    /* Modified by Priscila - inicio */
    User setUser() {
    	return (users[Sim.random(0, (users_number - 1), rn)]);               
    } 
    
    /* User setUser() {
		return (Sim.random(0, 99, rn) < percentageClassA) ? users[Sim.random(0,
			(Const.N_USERS_A - 1), rn)] : users[Sim.random(Const.N_USERS_A,
			(Const.N_USERS_A + Const.N_USERS_B - 1), rn)];
    } */
    /* Modified by Priscila - fim */
    
    
    /**
     * Handles the arrival of some request:
     *     - updates data system;
     *     - schedules the requesting to a server;
     *     - schedules the arrival of a new request.
     */
    
    void handleArrival() {
		Sim.update_arrivals();
	
		event.id = Const.RQST_SRVR;
		Sim.schedule(event, 0.0);
	
		newArrival(Sim.expntl(Const.ARRIVAL_TIME, rn));
    }

    
    /**
     * Tries to reserve a server. If got success, the ending of the request
     * exection is scheduled. Other else, the request is queued.
     */
    void requestServer() {
		if (preemptiveRequest == Const.true_) {
		    if (Sim.preempt(server, event.token, event.priority) == Const.FREE) {
				event.id = Const.RLS_SRVR;
				Sim.schedule(event, getServiceTime(event.token.load));
			}
		}
		else if (Sim.request(server, event.token, event.priority) == Const.FREE) {
			event.id = Const.RLS_SRVR;
			Sim.schedule(event, getServiceTime(event.token.load));
		}
    }

    
    /**
     * Computes the service time of a request based on its load.
     * */
    static double getServiceTime(double load) {
    	return (load * 1.0);
    }

    
    /**
     * Releases a server, updates data system and writes to log 
     * the mean service time.
     * 
     * Log: [Col_1]\t[Col_2]\t[Col_3]\t[Col_4]
     *     Col_1 - Release time;
     *     Col_2 - Current mean response time of a class;
     *     Col_3 - cRt
     *     Col_4 - Queue Length
     */
    void releaseServer() {
		event.token.owner.update(event.token.timeStamp); // Update the current mean response time
		Sim.release(server, event.token, politics);
		Sim.update_completions(event.token);
	
		output[event.token.owner.userId].println(Sim.time() + "\t"
			+ event.token.owner.meanResponseTime + "\t"
			+ contracts[event.token.owner.userId] + "\t"
			+ Sim.state().facility[server].queue_length);
		
		/* output[event.token.owner.userId].println(Sim.time() + "\t"
				+ event.token.owner.meanResponseTime + "\t"
				+ contracts[event.token.owner.classType] + "\t"
				+ Sim.state().facility[server].queue_length); */
    }

    
    /**
     * Rounds off a number for 'Const.DECIMAL_PLACE' decimal places. 
     * */
    public double rounder(double number) {
		int decimalPlace = Const.DECIMAL_PLACE;
		BigDecimal bd = new BigDecimal(number);
		bd = bd.setScale(decimalPlace, BigDecimal.ROUND_HALF_UP);
		number = bd.doubleValue();
		
		return number;
	}
    
    
    /**
     * Calculates the percentage of requests that were answerd in accordance
     * with the contract specified per user class, leading in account a
     * certain acceptable deviation.
     * */
    public void calculateFulfilledContracts() {
		int index;
		int total = users_number;
		// int total = Const.N_USERS_A + Const.N_USERS_B;
		double perc;
		
		for(index = 0; index < total; index++) {
		    perc = 100.0 * ((double) users[index].satisfied / (double) users[index].requestCount);
		    fulfilledContracts[index] = rounder(perc);
		    
		    perc = 100.0 * ((double) users[index].latenciesBelowAverage / (double) users[index].requestCount);
		    latenciesBelowAverage[index] = rounder(perc);
		}
    }

    
    /**
     * Starts the model simulation.
     * 
     * @return Mean Residence Time for each Token.
     * */
    public double simulate() {
	
		System.out.println("Starting simulation " + simulNumber);
		// System.out.println("Starting simulation " + simulNumber + " - (" + percentageClassA + "%A, " + contractVariation + " Contract Variation)");
		switch(politics) {
			case Const.FIFO:
			    System.out.print("FIFO...");
			    break;
			case Const.EBS:
			    System.out.print("EBS...");
			    break;
			case Const.SJF:
			    System.out.print("SJF...");
			    break;
			default:
		}

		newArrival(0.0);
	
		while (tokenCount <= Const.N_REQUESTS) {
	
		    // Gets the first element from the Future Event List
		    event = Sim.next_event(0.0, Const.ASYNC);
	
		    // Events handler
		    switch (event.id) {
			    case Const.ARRIVAL:
				handleArrival();
				break;
		
			    case Const.RQST_SRVR:
				requestServer();
				break;
		
			    case Const.RLS_SRVR:
				releaseServer();
				break;
		
			    default:
				System.err.format("%nBad event id.%n");
				System.exit(Const.BAD_EVENT_ERROR);
		    }
	}

	calculateFulfilledContracts();
	System.out.println("Finished!");

	int index;
	int total = users_number;
	// int total = Const.N_USERS_A + Const.N_USERS_B;
	System.out.println("====================================================");
	for (int i = 0; i < users_number; i++) {
		System.out.println("Contract[" + i + "]: " + contracts[i]);
	}
	// System.out.println("Contract[A]: " + contracts[Const.CLASSE_A]);
	// System.out.println("Contract[B]: " + contracts[Const.CLASSE_B]);
	System.out.println("----------------------------------------------------");
	for (index = 0; index < users_number; index++) {
	    System.out.println("Mean service time[user" + index + "]: " + users[index].meanResponseTime
			    	  						+ " - Satisfied Rate: " + fulfilledContracts[index] + "%");
	}
	
	/* for (index = 0; index < Const.N_USERS_A; index++) {
	    System.out.println("ClassA mean service time[user"
			    + index
			    + "]: "
			    + users[index].meanResponseTime
			    + " - Satisfied Rate: "
			    + fulfilledContracts[index]
			    + "%");
	}
	for (; index < total; index++) {
	    System.out.println("ClassB mean service time[user"
			    + (index - Const.N_USERS_A)
			    + "]: "
			    + users[index].meanResponseTime
			    + " - Satisfied Rate: "
			    + fulfilledContracts[index]
			    + "%");
	} */
	System.out.println("====================================================");
	//	Sim.report_stats_file();
	Sim.report_stats();

	closeStreams();

	return Sim.get_mean_residence_time();
    }
}